Skip to content

feat: increase text and interactive elems contrast#2621

Open
alexdln wants to merge 5 commits into
npmx-dev:mainfrom
alexdln:feat/color-contrast
Open

feat: increase text and interactive elems contrast#2621
alexdln wants to merge 5 commits into
npmx-dev:mainfrom
alexdln:feat/color-contrast

Conversation

@alexdln
Copy link
Copy Markdown
Member

@alexdln alexdln commented Apr 24, 2026

🧭 Context

One of the issues with the a11y and a recurring topic with the community is poor text visibility. I've also experienced this myself. So, I'm trying to increase contrast. As usual, I did it quite carefully - changed the colors by 10% and added a new shade for the borders (I use it for inputs/selects and in the midtone for buttons [I think they need a redesign to make them more visible, not the borders])

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment May 13, 2026 6:29pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview May 13, 2026 6:29pm
npmx-lunaria Ignored Ignored May 13, 2026 6:29pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added foreground theme selector in Settings to customise text appearance with contrast level options
    • Foreground themes now accessible through the command palette
  • Style

    • Updated colour palette across light and dark themes for improved visual consistency
    • Refined button and input styling

Walkthrough

This PR introduces a foreground theme feature enabling users to select text colour contrast levels (muted, standard, contrast). Design system tokens are extended with a --border-elevated variable and component-specific CSS selectors scoped to data-fg-theme modes. Settings persistence and DOM synchronisation occur via a new useForegroundTheme() composable, with a UI picker component and command palette integration for theme selection. Tests cover hydration, accessibility, command palette flows, and state transitions.

Changes

Foreground Theme Feature

Layer / File(s) Summary
Design system tokens and theme colours
app/assets/main.css, lunaria/styles.ts, uno.theme.ts, app/components/Button/Base.vue, app/components/Input/Base.vue, app/components/Select/Base.vue, app/components/AppHeader.vue
CSS custom properties and Tailwind tokens are updated to support foreground theme variants (muted/standard/contrast) via data-fg-theme selectors. A new --border-elevated colour variable is introduced. Button, input, select, and header components are updated to use new elevated/muted token set for borders and backgrounds.
Core state management and settings persistence
shared/utils/constants.ts, app/composables/useSettings.ts, app/utils/prehydrate.ts
FOREGROUND_THEMES constant defines the three theme variants. AppSettings schema is extended with preferredForegroundTheme field. New useForegroundTheme() composable exposes theme list, selected theme ID, and a setter that synchronises the choice to document.documentElement.dataset.fgTheme and persists it to localStorage. Prehydration logic applies the saved theme before hydration.
UI components for theme selection
app/components/Settings/FgThemePicker.vue, app/pages/settings.vue
New FgThemePicker component renders theme variants as clickable colour swatches using hidden radio inputs, with SSR hydration via localStorage check. Settings page integrates the picker in the Appearance section below background themes.
Command palette integration for theme navigation
app/types/command-palette.ts, app/composables/useCommandPaletteGlobalCommands.ts
Command palette type gains 'foreground-themes' view variant. Global command composable wires in foreground theme state, computes label/preview values, maps theme variants into selectable command entries with active/selection logic, and registers the foreground-themes view in command definitions.
Internationalisation for UI strings
i18n/locales/en.json, i18n/schema.json
i18n schema and locale file are extended with foreground_theme_changed announcement key and a settings.foreground_themes block containing label, contrast, standard, and muted translation entries.
Test coverage for foreground theme feature
test/e2e/hydration.spec.ts, test/nuxt/a11y.spec.ts, test/nuxt/components/CommandPalette.spec.ts, test/nuxt/components/HeaderConnectorModal.spec.ts, test/nuxt/composables/use-command-palette-commands.spec.ts
E2E tests verify hydration succeeds with preferredForegroundTheme: 'contrast'. Accessibility tests cover the new component and its pooled theme audit. Command palette tests validate placeholder updates, preview queries, and group-label matching. Composable tests assert command badge presence and theme selection state logic. Mock settings are updated across test suites.

Possibly Related PRs

  • npmx-dev/npmx.dev#2663: Both PRs extend the shared AppSettings interface and persisted defaults in app/composables/useSettings.ts—this PR adds preferredForegroundTheme, while the retrieved PR adds timelineChart settings.

Suggested Reviewers

  • graphieros
  • serhalp
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: increase text and interactive elems contrast' accurately reflects the main objective of the changeset, which involves adjusting color contrast for improved text and interactive element visibility.
Description check ✅ Passed The description clearly explains the purpose of increasing contrast, mentions careful colour adjustments (~10% change), and introduces a new border shade for improved accessibility.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 24, 2026

Codecov Report

❌ Patch coverage is 72.22222% with 10 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/composables/useCommandPaletteGlobalCommands.ts 66.66% 3 Missing and 2 partials ⚠️
app/composables/useSettings.ts 63.63% 3 Missing and 1 partial ⚠️
app/components/Settings/FgThemePicker.vue 85.71% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
i18n/locales/en.json Source changed, localizations will be marked as outdated.
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/components/Settings/FgThemePicker.vue`:
- Around line 8-11: The selector construction using
settings.preferredForegroundTheme (variable id) can throw if id contains invalid
characters; update the prehydrate logic in FgThemePicker.vue to validate or
sanitize id before passing it to el.querySelector: either check that id exists
in the canonical list of theme ids used by this component (e.g., the array/prop
that holds available theme ids) and only build the selector when it matches, or
call CSS.escape(id) to safely escape the value before interpolation; ensure you
reference and use the same identifier names (settings.preferredForegroundTheme /
id and the el.querySelector(...) call) when applying the validation/sanitization
so querySelector cannot be given a malformed selector.

In `@app/utils/prehydrate.ts`:
- Around line 42-46: preferredForegroundTheme is read from settings and applied
directly to document.documentElement.dataset.fgTheme; validate it first against
the allowed set ["muted","standard","contrast"] before assigning. Update the
logic around settings.preferredForegroundTheme in prehydrate.ts to: check that
preferredForegroundTheme is a string and is one of the allowed ids (e.g., via a
small const ALLOWED_FG_THEMES = new Set([...]) or array.includes), and only then
set document.documentElement.dataset.fgTheme = preferredForegroundTheme;
otherwise skip the assignment (or fall back to a safe default).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f6954aee-32cc-45e6-ac0c-22c64796aaad

📥 Commits

Reviewing files that changed from the base of the PR and between c2c2754 and c7bab73.

📒 Files selected for processing (21)
  • app/assets/main.css
  • app/components/AppHeader.vue
  • app/components/Button/Base.vue
  • app/components/Input/Base.vue
  • app/components/Select/Base.vue
  • app/components/Settings/FgThemePicker.vue
  • app/composables/useCommandPaletteGlobalCommands.ts
  • app/composables/useSettings.ts
  • app/pages/settings.vue
  • app/types/command-palette.ts
  • app/utils/prehydrate.ts
  • i18n/locales/en.json
  • i18n/schema.json
  • lunaria/styles.ts
  • shared/utils/constants.ts
  • test/e2e/hydration.spec.ts
  • test/nuxt/a11y.spec.ts
  • test/nuxt/components/CommandPalette.spec.ts
  • test/nuxt/components/HeaderConnectorModal.spec.ts
  • test/nuxt/composables/use-command-palette-commands.spec.ts
  • uno.theme.ts

Comment on lines +8 to +11
const id = settings.preferredForegroundTheme
if (id) {
const input = el.querySelector<HTMLInputElement>(`input[value="${id}"]`)
if (input) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Harden selector construction against invalid persisted values.

Line 10 interpolates id from localStorage into a CSS selector. If that value is malformed, querySelector(...) can throw and abort this prehydrate callback. Validate id against known theme ids (or use CSS.escape(id)) before building the selector.

As per coding guidelines: "Ensure you write strictly type-safe code, for example by ensuring you always check when accessing an array value by index".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/Settings/FgThemePicker.vue` around lines 8 - 11, The selector
construction using settings.preferredForegroundTheme (variable id) can throw if
id contains invalid characters; update the prehydrate logic in FgThemePicker.vue
to validate or sanitize id before passing it to el.querySelector: either check
that id exists in the canonical list of theme ids used by this component (e.g.,
the array/prop that holds available theme ids) and only build the selector when
it matches, or call CSS.escape(id) to safely escape the value before
interpolation; ensure you reference and use the same identifier names
(settings.preferredForegroundTheme / id and the el.querySelector(...) call) when
applying the validation/sanitization so querySelector cannot be given a
malformed selector.

Comment thread app/utils/prehydrate.ts
Comment on lines +42 to +46
// Apply foreground accent
const preferredForegroundTheme = settings.preferredForegroundTheme
if (preferredForegroundTheme) {
document.documentElement.dataset.fgTheme = preferredForegroundTheme
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate preferredForegroundTheme before applying it to data-fg-theme.

Line 43 reads an untyped runtime value from localStorage and Line 45 applies it directly. Please guard it against the allowed ids (muted, standard, contrast) before setting document.documentElement.dataset.fgTheme, to keep prehydrate state deterministic and avoid invalid theme attributes.

As per coding guidelines: "Ensure you write strictly type-safe code, for example by ensuring you always check when accessing an array value by index".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/utils/prehydrate.ts` around lines 42 - 46, preferredForegroundTheme is
read from settings and applied directly to
document.documentElement.dataset.fgTheme; validate it first against the allowed
set ["muted","standard","contrast"] before assigning. Update the logic around
settings.preferredForegroundTheme in prehydrate.ts to: check that
preferredForegroundTheme is a string and is one of the allowed ids (e.g., via a
small const ALLOWED_FG_THEMES = new Set([...]) or array.includes), and only then
set document.documentElement.dataset.fgTheme = preferredForegroundTheme;
otherwise skip the assignment (or fall back to a safe default).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant